#!/usr/bin/python
# -*- coding: utf-8 -*-

''' lexerSLA.py

Module permettant de scanner un fichier sla.

2009-2010, Elias Capito et Jonathan Neuhaus, HE-Arc

'''

import ply.lex as lex
import re
import sys

# Noms de variables interdits
forbidden_words = (
	'auto', 'break', 'char', 'continue', 'default',
	'do', 'double', 'extern', 'float', 'goto', 
	'int', 'long', 'register', 'short', 'sizeof', 
	'static', 'struct', 'switch', 'typedef', 
	'union', 'unsigned', 'while'
)

# Mots réservés
reserved_words = (
	# Boucles
	'loop', 'for',
	# Structures de contrôle
	'if', 'elif', 'else',
	'select','case',
	# Instructions d'interruption 
	'return', 'break',
	# Déclaration de fonction
	'func',
	# Opérations de bit
	'enable', 'disable', 'flip', 'read', 'write',
	# Atmel: Configurations
	'configDir', 'configPullUp', 'configUSART',	'configPWMT0',
	'on', 'off', 'in', 'out',
)

# Tokens
tokens = (
	# Général
	'SINGLE_QUOTE', 'DOUBLE_QUOTES', 'NUMBER', 
	'INDENT', 'DEDENT',
	'NEWLINE',
	# Variable
	'IDENTIFIER',
	# Opérations
	'ADD_OP', 'MUL_OP',
	'SHIFT', 'EQUALITY', 'COMPARE', 'BINARY',
	# Atmel
	'PIN',	
  # Introduction des mots réservés à la liste des tokens
) + tuple(map(lambda s:s.upper(),reserved_words))

# Tokens à un caractère
literals = '()=~;'

# Pile pour stocker les niveaux d'indentations
levelsStack = [0]

# Ignorer les commentaires avec §§ et §° °§
def t_COMMENT(t):
	r'(§°(.|\n)*°§)|(§§.*)'
	# Ajoute le nombre de ligne dans comprises dans les commentaires
	t.lexer.lineno += t.value.count('\n')

def t_DOUBLE_QUOTES(t):
	r'".+"'
	return t
	
def t_SINGLE_QUOTE(t):
	r'\'(\\.|.)\''
	return t
	
def t_NUMBER(t):
	r'\d+'
	return t

def t_PIN(t):
	r'\bPIN[A-Z][0-7]?\b'
	return t

def t_IDENTIFIER(t):
	r'[A-Za-z_]\w*'
	# Contrôle si une variable est un nom interdit
	if t.value in forbidden_words:
		print "*** ERREUR : Variable avec un nom interdit : %s ligne %s ***" % (t.value,t.lineno)
		sys.exit()
	# Contrôle si une variable est un mot réservé
	if t.value in reserved_words:
		t.type = t.value.upper()
	return t

def t_ADD_OP(t):
	r'[+-]'
	return t

def t_MUL_OP(t):
	r'[*/%]'
	return t

def t_SHIFT(t):
	r'<<|>>'
	return t

def t_EQUALITY(t):
	r'==|<>|!='
	return t

def t_COMPARE(t):
	r'[<>]=?'
	return t

def t_BINARY(t):
	r'&(&)?|\|(\|)?|\^'
	return t
	
# Code repris pour gérer l'indentation
#Modification: Supprime token NEWLINE en fin de fichier
# Definition of the token "NEWLINE"
# Handle DEDENT, INDENT and NEWLINE
def t_NEWLINE(t):
	r'[\n]+[\t\s]*'
	
	cleanValue = t.value;
	currentLevel = len(cleanValue.strip('\n'));
	t.lexer.lineno += len(cleanValue) - currentLevel
	t.value = 1
	
	# INDENT
	if currentLevel > levelsStack[-1]:
		t.type = 'INDENT'
		levelsStack.append(currentLevel)
	
	#DEDENT
	elif currentLevel < levelsStack[-1]:
		t.type = 'DEDENT'
		seeking = True
		while(seeking):
			levelsStack.pop()
			if len(levelsStack) == 0 or currentLevel > levelsStack[-1]:
				print 'Unindent does not match any outer indentation level'
				return
			elif currentLevel < levelsStack[-1]:
				# May work with the IndentLexer
				t.value += 1
			else:
				seeking = False
	# NEWLINE
	else:
		# Création d'un clone du lexer, afin de vérifier si le prochain token est Node
		clone = t.lexer.clone()
		if clone.token():
			t.type = 'NEWLINE'
		else:
			return 
	
	return t

# Tokens ignorés
t_ignore  = ' \t'

def t_error(t):
	print "***  ERREUR : Caractere illegal '%s' ***" % repr(t.value[0])
	t.lexer.skip(1)

# Code repris pour gérer l'indentation
# Modification: Génère token manquant en fin de fichier
class IndentLexer(object):
	def __init__(self, debug=0, optimize=0, lextab='lextab', reflags=0):
		self.lexer = lex.lex(debug=debug, optimize=optimize, lextab=lextab, reflags=reflags)
	
	def filter_tokens(self):
		while 1:
			t = self.lexer.token()
			if not t: 
				# Génère Token DEDENT manquant en fin de fichier
				for i in range(len(levelsStack)-1):
					dedent = lex.LexToken()
					dedent.type = 'DEDENT'
					dedent.lineno = self.lexer.lineno
					dedent.value = 1
					yield dedent
				return
			if t.type == 'DEDENT':
				for i in range(t.value):
					yield t
			else :
				yield t
	
	def input(self, s):
		self.lexer.input(s)
		self.tokens = self.filter_tokens()
	
	def token(self):
		try:
			return self.tokens.next()
		except StopIteration:
			return None

lex.lex()

# Fonction permettant de scanner 
def scanSLA(program):
	lexer = IndentLexer(reflags=re.UNICODE)
	lexer.input(program)
	return lexer
	
if __name__ == "__main__":
	try:
		prog = file(sys.argv[1]).read()
		lexer = scanSLA(prog)
		
		while 1:
			tok = lexer.token()
			if not tok: break
			print "line %d: %s(%s)" % (tok.lineno, tok.type, tok.value)
	except IOError:
		print "*** ERREUR : Le module lexerSLA n'a pas trouve le fichier ***"